package kandl.automation.http;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;

import javax.net.ssl.SSLContext;

import java.io.IOException;


public class BasicAuthHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {

    private static final int DEFAULT_MAX_CONNECTIONS_PER_HOST = 20;
    private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20;
    private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
    private static final int DEFAULT_CONNECTION_TIMEOUT = (10 * 1000);

    private int maxTotalConnections = DEFAULT_MAX_TOTAL_CONNECTIONS;
    private int defaultMaxConnectionsPerRoute = DEFAULT_MAX_CONNECTIONS_PER_HOST;
    private int connectTimeout = DEFAULT_CONNECTION_TIMEOUT;
    private int readTimeout = DEFAULT_READ_TIMEOUT_MILLISECONDS;

    private String password;
    private String userName;
    private String authHeader;

    public BasicAuthHttpRequestFactory() {
    }

    public void init() throws Exception {
        DefaultHttpClient httpClient = new DefaultHttpClient(createConnectionManager());

        authHeader = "Basic " + new String(Base64.encodeBase64((userName + ":" + password).getBytes()));
        // This should really be performed by setting the CredentialsProvider on the client
        // But that doesn't appear to be working, so add the header manually.
        httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
            @Override
            public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
                // Inject the Basic auth header into ALL requests...
                httpRequest.setHeader(HttpHeaders.AUTHORIZATION, getAuthorizationHeader());
                // Hack the Content-type as the TestRail server is fussy about this
                httpRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
            }
        });

        super.setReadTimeout(readTimeout);
        super.setConnectTimeout(connectTimeout);
        setHttpClient(httpClient);
    }

    private String getAuthorizationHeader() {
        return authHeader;
    }

    private ClientConnectionManager createConnectionManager() throws Exception {
        SchemeRegistry schemeRegistry = createSchemeRegistry();
        PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(schemeRegistry);
        connectionManager.setMaxTotal(maxTotalConnections);
        connectionManager.setDefaultMaxPerRoute(defaultMaxConnectionsPerRoute);
        return connectionManager;
    }

    private SchemeRegistry createSchemeRegistry() throws Exception {
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
        schemeRegistry.register(new Scheme("https", 443, getSslSocketFactory()));
        return schemeRegistry;
    }

    private SSLSocketFactory getSslSocketFactory() throws Exception {
        return new SSLSocketFactory(SSLContext.getDefault());
    }

    public void setMaxTotalConnections(int maxTotalConnections) {
        this.maxTotalConnections = maxTotalConnections;
    }

    public void setDefaultMaxConnectionsPerRoute(int defaultMaxConnectionsPerRoute) {
        this.defaultMaxConnectionsPerRoute = defaultMaxConnectionsPerRoute;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Override
    public void setConnectTimeout(int timeout) {
        this.connectTimeout = timeout;
    }

    @Override
    public void setReadTimeout(int timeout) {
        this.readTimeout = timeout;
    }
}